package com.spzx.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.spzx.common.core.exception.ServiceException;
import com.spzx.common.redis.cache.GuiguCache;
import com.spzx.product.domain.*;
import com.spzx.product.mapper.ProductDetailsMapper;
import com.spzx.product.mapper.ProductMapper;
import com.spzx.product.mapper.ProductSkuMapper;
import com.spzx.product.mapper.SkuStockMapper;
import com.spzx.product.service.ProductService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {

    @Autowired
    private ProductSkuMapper productSkuMapper;

    @Autowired
    private SkuStockMapper skuStockMapper;

    @Autowired
    private ProductDetailsMapper productDetailsMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    //查询商品列表
    @Override
    public List<Product> selectProductList(Product product) {
        List<Product> list = baseMapper.selectProductList(product);
        return list;
    }

    //添加商品
    @Transactional
    @Override
    public int insertProduct(Product product) {
        //0 操作多张表，添加到事务里面
        //1 添加商品基本信息到product表
        baseMapper.insert(product);

        //2 获取前端传递过来商品所有sku列表list集合 productSkuList
        List<ProductSku> productSkuList = product.getProductSkuList();

        //3 把sku列表list集合遍历，得到每个sku对象，把每个sku添加product_sku
        for (int i = 0; i < productSkuList.size(); i++) {
            ProductSku productSku = productSkuList.get(i);
            //设置一些sku其他值
            productSku.setSkuCode(product.getId() + "_" + i);
            productSku.setProductId(product.getId());
            String skuName = product.getName() + " " + productSku.getSkuSpec();
            productSku.setSkuName(skuName);
            //把每个sku添加product_sku表
            productSkuMapper.insert(productSku);

            //4 把每个sku和库存量，添加sku_stock
            SkuStock skuStock = new SkuStock();
            skuStock.setSkuId(productSku.getId());
            skuStock.setTotalNum(productSku.getStockNum());
            skuStock.setLockNum(0);
            skuStock.setAvailableNum(productSku.getStockNum());
            skuStock.setSaleNum(0);
            skuStockMapper.insert(skuStock);
        }

        //5 添加商品product_details
        // 商品图片 image_urls
        // detailsImageUrlList集合 转换字符串
        ProductDetails productDetails = new ProductDetails();

        List<String> detailsImageUrlList = product.getDetailsImageUrlList();
        // [01.jpg,02.jpg]  ==  01.jpg,02.jpg
        productDetails.setProductId(product.getId());

        String detailsImageUrl = String.join(",", detailsImageUrlList);
        productDetails.setImageUrls(detailsImageUrl);

        productDetailsMapper.insert(productDetails);

        return product.getId().intValue();
    }

    //获取商品详细信息
    @Override
    public Product selectProductById(Long id) {
        //1 根据商品id获取商品基本信息，查询product表
        Product product = baseMapper.selectById(id);

        //2 根据商品id获取商品所有sku列表
        LambdaQueryWrapper<ProductSku> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ProductSku::getProductId,id);
        List<ProductSku> productSkuList = productSkuMapper.selectList(wrapper);

        //2.1 因为每个sku有对应库存量
        //2.2 查询每个sku库存量封装到每个ProductSku对象里面
        //第一种 productSkuList遍历，得到每个sku，根据每个skuid查询库存表得到库存量，设置对象
//        productSkuList.forEach(productSku -> {
//            Long skuId = productSku.getId();
//            //根据skuId查询库存
//            LambdaQueryWrapper<SkuStock> wrapper1 = new LambdaQueryWrapper<>();
//            wrapper1.eq(SkuStock::getSkuId,skuId);
//            SkuStock skuStock = skuStockMapper.selectOne(wrapper1);
//            Integer totalNum = skuStock.getTotalNum();
//
//            productSku.setStockNum(totalNum);
//        });

        //第二种 stream流数据处理
        //1 从productSkuList获取所有id
        //List<ProductSku>
        List<Long> skuIdList =
                productSkuList.stream().map(ProductSku::getId).collect(Collectors.toList());

        //2 拿着所有skuId集合查询库存表，对应库存信息
        LambdaQueryWrapper<SkuStock> wrapperSkuStock = new LambdaQueryWrapper<>();
        wrapperSkuStock.in(SkuStock::getSkuId,skuIdList);
        List<SkuStock> skuStockList = skuStockMapper.selectList(wrapperSkuStock);

        //3 第二步查询sku所有库存信息list ，转换map集合
        // key:skuId  value:库存量
        Map<Long, Integer> map =
                skuStockList.stream()
                        .collect(Collectors.toMap(SkuStock::getSkuId, SkuStock::getTotalNum));

        //4 把productSkuList遍历，根据skuId到第三步map里面找到对应库存，设置到productSku
        productSkuList.forEach(productSku -> {
            //获取skuId
            Long productSkuId = productSku.getId();
            //根据skuid查询map，获取对应库存量
            Integer num = map.get(productSkuId);
            productSku.setStockNum(num);
        });

        //3 把查询商品所有sku列表封装product里面
        product.setProductSkuList(productSkuList);

        //4 根据商品id获取商品详情数据  图片列表
        LambdaQueryWrapper<ProductDetails> wrapperDetails = new LambdaQueryWrapper<>();
        wrapperDetails.eq(ProductDetails::getProductId,id);
        ProductDetails productDetails = productDetailsMapper.selectOne(wrapperDetails);

        String imageUrls = productDetails.getImageUrls();
        // 01.jpg,02.jpg
        //字符串 imageUrls 转换数组
        String[] split = imageUrls.split(",");
        //把数组转换list集合
        List<String> list = Arrays.asList(split);
        //5 把查询商品图片列表封装product里面
        product.setDetailsImageUrlList(list);

        //6 返回封装完成product对象
        return product;
    }

    //修改
    @Transactional
    @Override
    public int updateProduct(Product product) {
        //修改商品信息
        baseMapper.updateById(product);

        List<ProductSku> productSkuList = product.getProductSkuList();

        productSkuList.forEach(productSku -> {
            //修改商品SKU信息
            productSkuMapper.updateById(productSku);

            //修改商品库存
            SkuStock skuStock =
                    skuStockMapper.selectOne(new LambdaQueryWrapper<SkuStock>()
                            .eq(SkuStock::getSkuId, productSku.getId()));
            skuStock.setTotalNum(productSku.getStockNum());

            int availableNum = skuStock.getTotalNum() - skuStock.getLockNum();

            skuStock.setAvailableNum(availableNum);
            skuStockMapper.updateById(skuStock);
        });

        //修改商品详细信息
        ProductDetails productDetails =
                productDetailsMapper.selectOne(new LambdaQueryWrapper<ProductDetails>().eq(ProductDetails::getProductId, product.getId()));
        productDetails.setImageUrls(String.join(",", product.getDetailsImageUrlList()));
        productDetailsMapper.updateById(productDetails);
        return 1;
    }

    //删除
    @Transactional
    @Override
    public int deleteProductByIds(Long[] ids) {
        //1 根据商品id删除product
        baseMapper.deleteBatchIds(Arrays.asList(ids));

        //2 删除skuId，删除sku里面库存信息
        //获取商品所有skuId
        LambdaQueryWrapper<ProductSku> wrapper = new LambdaQueryWrapper<>();
        wrapper.in(ProductSku::getProductId,ids);
        List<ProductSku> productSkuList = productSkuMapper.selectList(wrapper);

        //productSkuList  获取skuId值
        List<Long> skuIdList =
                productSkuList.stream().map(ProductSku::getId).collect(Collectors.toList());

        //删除sku对应库存 删除条件 skuId
        LambdaQueryWrapper<SkuStock> wrapperSkuStock = new LambdaQueryWrapper<>();
        wrapperSkuStock.in(SkuStock::getSkuId,skuIdList);
        skuStockMapper.delete(wrapperSkuStock);

        //3 根据商品id删除product_sku
        productSkuMapper
                .delete(new LambdaQueryWrapper<ProductSku>().in(ProductSku::getProductId, ids));

        //4 根据商品id删除详情
        productDetailsMapper
                .delete(new LambdaQueryWrapper<ProductDetails>()
                        .in(ProductDetails::getProductId, ids));
        return 1;
    }

    //查询畅销商品
    @Override
    public List<ProductSku> getTopSale() {
        return productSkuMapper.selectTopSale();
    }

    //远程调用使用，商品列表
    @Override
    public List<ProductSku> selectProductSkuList(SkuQuery skuQuery) {
        return productSkuMapper.selectProductSkuList(skuQuery);
    }

    /////////////////////////////////////
    //redis底层代码实现分布式锁：  根据skuId获取sku信息
//    public ProductSku getProductSku1(Long skuId) {
//        //1 根据skuId查询redis，看redis是否存在查询数据
//        String dataKey = "product:sku:" + skuId;
//        ProductSku productSku = (ProductSku)redisTemplate.opsForValue().get(dataKey);
//
//        //2 如果redis有要查询数据，直接返回
//        if(productSku != null) {
//            return productSku;
//        }
//
//        //3 如果redis没有数据，查询mysql数据库
//        //3.1 添加分布式锁
//        String lockKey = "product:sku:lock:" + skuId;
//        String uuid = UUID.randomUUID().toString().replaceAll("-","");
//
//        Boolean ifAbsent =
//                redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, 10, TimeUnit.SECONDS);
//        if(ifAbsent) { //获取当前锁
//            try {
//                //3.2 根据skuId查询mysql，得到数据，把查询数据放到redis里面，返回查询数据
//                ProductSku productSkuFromDB = this.getProductSkuFromDB(skuId);
//                redisTemplate.opsForValue().set(dataKey,productSkuFromDB,10,TimeUnit.MINUTES);
//
//                return productSkuFromDB;
//            }catch (Exception e) {
//
//            }finally {
//                //3.3 释放分布式锁
//                String scriptText = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
//                        "then\n" +
//                        "    return redis.call(\"del\",KEYS[1])\n" +
//                        "else\n" +
//                        "    return 0\n" +
//                        "end";
//                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
//                redisScript.setScriptText(scriptText);
//                redisScript.setResultType(Long.class);
//                redisTemplate.execute(redisScript, Arrays.asList(lockKey), uuid);
//            }
//        } else {
//            //加锁没有成功
//
//            try {
//                //5.获取锁失败则自旋（业务要求必须执行）
//                Thread.sleep(200);
//            } catch (InterruptedException e) {
//                throw new RuntimeException(e);
//            }
//            return this.getProductSku(skuId);
//        }
//
//        return null;
//    }

    ////////////////////////////
    @Autowired
    private RedissonClient redissonClient;

    @GuiguCache(prefix = "product:")
    public ProductSku getProductSku(Long skuId) {
        return productSkuMapper.selectById(skuId);
    }

    //使用Redisson实现分布式锁：根据skuId获取sku信息
   // @GuiguCache(prefix = "product:")
    public ProductSku getProductSku2(Long skuId) {
        //1 根据skuId查询redis，看redis是否存在查询数据
        String dataKey = "product:sku:" + skuId;
        ProductSku productSku = (ProductSku)redisTemplate.opsForValue().get(dataKey);

        //2 如果redis有要查询数据，直接返回
        if(productSku != null) {
            return productSku;
        }

        //3 如果redis没有数据，查询mysql数据库
        //3.1 添加分布式锁
        String lockKey = "product:sku:lock:" + skuId;
        //获取到锁对象
        RLock rLock = redissonClient.getLock(lockKey);
        try {//加分布式锁
            //tryLock第一个参数：等待时间  第二个参数：锁过期时间
            boolean tryLock = rLock.tryLock(5, 10, TimeUnit.SECONDS);
            if(tryLock) {
                try {
                    //3.2 根据skuId查询mysql，得到数据，把查询数据放到redis里面，返回查询数据
                    ProductSku productSkuFromDB = this.getProductSkuFromDB(skuId);
                    redisTemplate.opsForValue().set(dataKey, productSkuFromDB, 10, TimeUnit.MINUTES);

                    return productSkuFromDB;
                }finally {
                    //释放分布锁
                    rLock.unlock();
                }
            } else {
                try {
                    //5.获取锁失败则自旋（业务要求必须执行）
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                return this.getProductSku(skuId);
            }

        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    //查询返回sku信息
    public ProductSku getProductSkuFromDB(Long skuId) {
        return productSkuMapper.selectById(skuId);
    }

    //根据商品id获取商品基本信息
    @Override
    public Product getProduct(Long id) {
        Product product = baseMapper.selectById(id);
        return product;
    }

    //根据skuId获取价格数据
    @Override
    public SkuPrice getSkuPrice(Long skuId) {
        LambdaQueryWrapper<ProductSku> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ProductSku::getId,skuId);
        wrapper.select(ProductSku::getSalePrice,ProductSku::getMarketPrice);
        ProductSku productSku = productSkuMapper.selectOne(wrapper);

        SkuPrice skuPrice = new SkuPrice();
        // productSku - skuPrice
        BeanUtils.copyProperties(productSku,skuPrice);

        return skuPrice;
    }

    //根据商品id获取详细信息(商品图片信息)
    @Override
    public ProductDetails getProductDetails(Long id) {
        return productDetailsMapper.selectOne(new LambdaQueryWrapper<ProductDetails>()
                .eq(ProductDetails::getProductId, id));
    }

    //根据商品id获取规格数据
    @Override
    public Map<String, Long> getSkuSpecValue(Long id) {
        //1 根据商品id获取商品里面所有sku列表
        LambdaQueryWrapper<ProductSku> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ProductSku::getProductId,id);
        wrapper.select(ProductSku::getId,ProductSku::getSkuSpec);
        List<ProductSku> productSkuList = productSkuMapper.selectList(wrapper);

        //2 遍历sku集合，得到每个sku数据，把每个sku数据封装map集合里面
        Map<String, Long> map = new HashMap<>();
        productSkuList.forEach(item->{
            map.put(item.getSkuSpec(),item.getId());
        });
        return map;
    }

    //根据skuId获取销售量
    @Override
    public SkuStockVo getSkuStock(Long skuId) {
        SkuStock skuStock = skuStockMapper.selectOne(new LambdaQueryWrapper<SkuStock>().eq(SkuStock::getSkuId, skuId));
        SkuStockVo skuStockVo = new SkuStockVo();
        BeanUtils.copyProperties(skuStock,skuStockVo);
        return skuStockVo;
    }

    //根据skuId列表获取对应价格数据
    @Override
    public List<SkuPrice> getSkuPriceList(List<Long> skuIdList) {
        //1 根据skuIdList查询sku表，得到对应sku集合
        LambdaQueryWrapper<ProductSku> wrapper = new LambdaQueryWrapper<>();
        wrapper.in(ProductSku::getId,skuIdList);
        List<ProductSku> productSkuList = productSkuMapper.selectList(wrapper);

        //2 List<ProductSku>-- List<SkuPrice>
        List<SkuPrice> skuPriceList = productSkuList.stream().map(productSku -> {
            SkuPrice skuPrice = new SkuPrice();
            skuPrice.setSkuId(productSku.getId());
            skuPrice.setSalePrice(productSku.getSalePrice());
            return skuPrice;
        }).collect(Collectors.toList());
        return skuPriceList;
    }

    //下单检验库存远程调用使用接口
    @Override
    public String checkAndLock(String orderNo, List<SkuLockVo> skuLockVoList) {
        //1 生成两个redis里面key
        //第一个key是用于重复提交
        String key = "sku:checkAndLock:" + orderNo;
        //第二个key用于存储最终数据
        String dataKey = "sku:lock:data:" + orderNo;

        //2 使用setnx判断重复提交
        Boolean ifAbsent =
                redisTemplate.opsForValue().setIfAbsent(key, orderNo, 30, TimeUnit.MINUTES);

        if(!ifAbsent) {//不是第一次提交
            //如果存在，则已执行过库存锁定
            if(redisTemplate.hasKey(dataKey)) {
                return "";
            } else {
                return "重复提交";
            }
        }

        //3 skuLockVoList遍历，得到每个SkuLockVo
        //根据是否充足，设置SkuLockVo不同的值
        skuLockVoList.forEach(skuLockVo -> {
            //拿着每个SkuLockVo值查询数据库，库存量是否充足
            //select * from  sku_stock st where st.sku_id=? and st.available_num>?
            SkuStock skuStock =
                    skuStockMapper.check(skuLockVo.getSkuId(),skuLockVo.getSkuNum());
            if(skuStock == null) { //库存量不足
                skuLockVo.setIsHaveStock(false);
            } else {
                skuLockVo.setIsHaveStock(true);
            }
        });

        //4 判断库存检验完成之后 skuLockVoList里面，
        //4.1 如果skuLockVoList里面每个SkuLockVo有任何一个库存量不足，找到不足的商品，返回提示信息
        //判断skuLockVoList里面每个SkuLockVo，是否存在库存不足对象，直接返回提示信息
        if(skuLockVoList.stream().anyMatch(skuLockVo -> !skuLockVo.getIsHaveStock())) {
            // 获取所有锁定成功的商品，遍历解锁库存
            StringBuffer result = new StringBuffer();
            //获取库存不足对象 
            List<SkuLockVo> noSkuStockList = skuLockVoList.stream()
                    .filter(skuLockVo -> !skuLockVo.getIsHaveStock())
                    .collect(Collectors.toList());

            for(SkuLockVo noHaveStockSkuLockVo : noSkuStockList) {
                //解除去重
                this.redisTemplate.delete(key);
                result.append("商品: " + noHaveStockSkuLockVo.getSkuId() + " 库存不足; ");
            }

            //锁定失败，解除
            redisTemplate.delete(key);
            // 响应锁定状态
            return result.toString();

        } else {
            //4.2 如果skuLockVoList所有商品库存量都充足，锁定库存
            skuLockVoList.forEach(skuLockVo -> {
                int rows = skuStockMapper.lock(skuLockVo.getSkuId(),skuLockVo.getSkuNum());
                if(rows == 0) {
                    //解除去重
                    this.redisTemplate.delete(key);
                    throw new ServiceException("库存锁定失败");
                }
            });
        }

        //5 把最终数据放到redis里面
        redisTemplate.opsForValue().set(dataKey,skuLockVoList);

        return "";
    }

    //解锁
    @Override
    public void unlock(String orderNo) {
        //1 生成两个key
        String key = "sku:unlock:" + orderNo;
        String dataKey = "sku:lock:data:" + orderNo;
        
        //2 判断重复提交
        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(key, orderNo, 10, TimeUnit.MINUTES);
        if(!ifAbsent) {
            return;
        }
    
        //3 根据orderNo从redis把数据获取出来
        //前面操作中，在检查和锁定的方法里面，把商品库存数据已经放到redis里面
        List<SkuLockVo> skuLockVoList = (List<SkuLockVo>)redisTemplate.opsForValue().get(dataKey);
        //把取出来数据非空判断
        if (CollectionUtils.isEmpty(skuLockVoList)){
            return ;
        }

        //4 遍历集合，得到每个skulockVo，调用方法把每个对象进行解锁
        skuLockVoList.forEach(skuLockVo -> {
            int rows = skuStockMapper.unlock(skuLockVo.getSkuId(), skuLockVo.getSkuNum());
            if(rows == 0) {
                //解除去重
                this.redisTemplate.delete(key);
                throw new ServiceException("解锁出库失败");
            }
        });

        redisTemplate.delete(dataKey);
    }

    //扣减库存
    @Override
    public void minus(String orderNo) {
        //1 生成两个key
        String key = "sku:unlock:" + orderNo;
        String dataKey = "sku:lock:data:" + orderNo;

        //2 判断重复提交
        Boolean ifAbsent = redisTemplate.opsForValue()
                .setIfAbsent(key, orderNo, 10, TimeUnit.MINUTES);
        if(!ifAbsent) {
            return;
        }

        //3 根据orderNo从redis把数据获取出来
        //前面操作中，在检查和锁定的方法里面，把商品库存数据已经放到redis里面
        List<SkuLockVo> skuLockVoList =
                (List<SkuLockVo>)redisTemplate.opsForValue().get(dataKey);
        //把取出来数据非空判断
        if (CollectionUtils.isEmpty(skuLockVoList)){
            return ;
        }

        //4 遍历集合，得到每个skulockVo，调用方法把每个对象进行解锁
        skuLockVoList.forEach(skuLockVo -> {
            int rows = skuStockMapper.minus(skuLockVo.getSkuId(), skuLockVo.getSkuNum());
            if(rows == 0) {
                //解除去重
                this.redisTemplate.delete(key);
                throw new ServiceException("扣减失败");
            }
        });

        redisTemplate.delete(dataKey);
    }

//    public static void main(String[] args) {
//        List<String> list = new ArrayList<>();
//        list.add("01.jpg");
//        list.add("02.jpg");
//
//        String join = String.join(",", list);
//        System.out.println(join);
//    }

    public static void main(String[] args) {
        List<SkuLockVo> skuLockVoList = new ArrayList<>();
        SkuLockVo skuLockVo1 = new SkuLockVo();
        skuLockVo1.setSkuId(1l);
        skuLockVo1.setSkuNum(2);
        skuLockVo1.setIsHaveStock(true);

        SkuLockVo skuLockVo2 = new SkuLockVo();
        skuLockVo2.setSkuId(2l);
        skuLockVo2.setSkuNum(2);
        skuLockVo2.setIsHaveStock(false);

        SkuLockVo skuLockVo3 = new SkuLockVo();
        skuLockVo3.setSkuId(3l);
        skuLockVo3.setSkuNum(2);
        skuLockVo3.setIsHaveStock(true);

        skuLockVoList.add(skuLockVo1);
        skuLockVoList.add(skuLockVo2);
        skuLockVoList.add(skuLockVo3);

        //stream
        if(skuLockVoList.stream().anyMatch(skuLockVo -> skuLockVo.getIsHaveStock() == false)) {
            //skuLockVoList里面任何一个对象值 getIsHaveStock是否false
            System.out.println("存在false....");
            //获取集合里面false数据
            List<SkuLockVo> lockVoList =
                    skuLockVoList.stream()
                            .filter(skuLockVo -> skuLockVo.getIsHaveStock() == false)
                            .collect(Collectors.toList());
            System.out.println(lockVoList);

        } else {
            //getIsHaveStock都是true
            System.out.println("都是true....");
        }

    }

}
